package net.m510.autosurfer.common;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

public class DocumentCapturer {
	private static final Logger log = Logger.getLogger(DocumentCapturer.class.getName());
	private CloseableHttpClient client;
	private HttpClientContext context;
	private File outDir;
	private String outFileName;
	private String charset;
	private URI baseUri;
	private File extDir;
	private boolean isShallow;

	public DocumentCapturer() {
		super();
	}
	public DocumentCapturer(File outDir, String outFileName, String charset) {
		super();
		this.outDir = outDir;
		this.outFileName = outFileName;
		this.charset = charset;
		this.isShallow = true;
	}
	public DocumentCapturer(CloseableHttpClient client,HttpClientContext context,File outDir, String outFileName, String charset, URI baseUri) {
		super();
		this.client = client;
		this.context = context;
		this.outDir = outDir;
		this.outFileName = outFileName;
		this.charset = charset;
		this.baseUri = baseUri;
		this.extDir = new File(this.outDir,getBaseName(outFileName)+".files");
		this.isShallow = false;
	}
	
	public List<File> capture(Document doc) throws IOException {
		List<File> l = new ArrayList<File>();
		File outFile = new File(outDir,outFileName);
		l.add(outFile);
		
		// 出力ディレクトリ作成
		outDir.mkdir();
		
		// 外部ファイルダウンロード
		List<File> extFiles = null;
		if ( !isShallow ) {
			extFiles = saveExternalFiles(doc,baseUri,extDir);
			l.addAll(extFiles);
		}
		
		// htmlの保存
		Document doclone = doc.clone();
		if ( !isShallow && extFiles.size() > 0 ) {
			// パス情報を保存先のローカルディレクトリに書き換え
			ExternalContentsManager.rewriteContentPath(doclone, extDir.getName());
		}
		saveDocument(doclone,outFile);
		return l;
	}
	public List<File> saveExternalFiles(Document doc,URI baseUri, File dir) throws IOException {
		Elements elems = ExternalContentsManager.selectExternalContents(doc);
		log.fine("save elems="+elems.toString());
		if ( elems.size() > 0 ) {
			extDir.mkdir();
		}
		List<File> files = downloadFiles(baseUri,elems,dir);
		return files;
	}
	private List<File> downloadFiles(URI baseUri, Elements elems, File outDir ) throws IOException {
		List<File> l = new ArrayList<File>();
		// URIの重複を排除したリストを作る
		Set<String> uriset = ExternalContentsManager.asUriSet(elems, baseUri);
		// URIごとにファイルにダウンロード
		for (String uristr : uriset) {
			URI uri = URI.create(uristr);
			// 出力ファイルを生成
			File out = new File(outDir,new File(uri.getPath()).getName());
			l.add(out);
			//　ダウンロード
			downloadFile(uri,out);
		}
		return l;
	}
	private void downloadFile(URI uri,File out) throws IOException {
		FileGetProcess p = new FileGetProcess(client, context, out, uri);
		log.fine("downloading ["+uri+"] to ["+out+"]");
		p.process(null);
	}
	
	public File saveDocument(Document doc,File file,String charset) throws IOException {
		ByteArrayInputStream in = new ByteArrayInputStream(new String(doc.html()).getBytes(charset));
		BufferedOutputStream out = null;
		try {
			out = new BufferedOutputStream(new FileOutputStream(file));
			int buf;
			while ( (buf = in.read()) != -1 ) {
				out.write(buf);
			}
		} finally {
			if ( out != null ) {
				out.flush();
				out.close();
			}
		}
		return file;
	}
	public File saveDocument(Document doc,File file) throws IOException {
		return saveDocument(doc, file, charset);
	}
	
	private String getBaseName(String fileName) {
		if ( fileName == null ) return null;
		int i = fileName.lastIndexOf(".");
		if ( i != -1 ) return fileName.substring(0,i);
		return fileName;
	}

	public boolean isShallow() {
		return isShallow;
	}

	public void setShallow(boolean isShallow) {
		this.isShallow = isShallow;
	}
}
